#include "QLibSsh/sshsftpconnection.h"

#include <libssh/sftp.h>
#include <QDebug>
#include <unistd.h>
#include <fcntl.h>
#include <QThread>

#include "QLibSsh/sshsession.h"

SshSftpConnection::SshSftpConnection(SshLoginKeys loginKeys, QObject* parent)
    : QObject(parent)
    , mSshLoginKeys(std::move(loginKeys))
{
}

SshSftpConnection::SshSftpConnection(SshSftpConnection&& other) noexcept {
    this->sshSession = other.sshSession;
    this->sftp.Swap(other.sftp);
    this->sshSession.Swap(other.sshSession);
    this->mSshLoginKeys = std::move(other.mSshLoginKeys);
}

SshSftpConnection& SshSftpConnection::operator=(SshSftpConnection&& other) noexcept {
    if (this == &other) {
        return *this;
    }
    this->sshSession = other.sshSession;
    this->sftp.Swap(other.sftp);
    this->sshSession.Swap(other.sshSession);
    this->mSshLoginKeys = std::move(other.mSshLoginKeys);

    return *this;
}

bool SshSftpConnection::InitSftp(const SshSession& sshSession) {
    sftp = SftpSession(sftp_new(sshSession.Get()));

    if (sftp.IsNull()) {
        qCritical("SshSftpConnection::connectSftp() 222, %s", ssh_get_error(sshSession.Get()));
        return false;
    }

    if (sftp_init(sftp.Get()) < 0) {
        qCritical("SshSftpConnection::connectSftp() 333, %s", ssh_get_error(sshSession.Get()));
        return false;
    }

    return true;
}

SshSftpConnection::SftpDir SshSftpConnection::OpenDir(const char* path) const {
    const auto dir = sftp_opendir(sftp.Get(), path);
    if (dir == nullptr) {
        return SftpDir(nullptr);
    }
    return SftpDir(dir);
}

bool SshSftpConnection::CreateDir(const char *path, const mode_t mode) const {
    if (sftp_mkdir(sftp.Get(), path, mode) != SSH_OK) {
        return false;
    }
    return true;
}

bool SshSftpConnection::RemoveDir(const char *path) const {
    if (sftp_rmdir(sftp.Get(), path) != SSH_OK) {
        return false;
    }
    return true;
}

SshSftpConnection::SftpFile SshSftpConnection::OpenFile(
    const char* filePath, const int accessType, const mode_t mode) const {
    const auto file = sftp_open(sftp.Get(), filePath, accessType, mode);
    if (file == nullptr) {
        return SftpFile(nullptr);
    }
    return SftpFile(file);
}

QByteArray SshSftpConnection::ReadFile(const SftpFile& file) {
    int len {};
    char data[4096] = {0};
    QByteArray buffer;
    while ((len = sftp_read(file.Get(), data, 4096)) > 0) {
        // 通过信号再转发给所需线程
        buffer.append(data, len);
    }

    return buffer;
}

bool SshSftpConnection::WriteFile(const SftpFile& file, const QByteArray& buffer) {
    size_t offset {0};
    const size_t length = buffer.length();

    while (offset < length) {
        const size_t wLength = length - offset > 4096 ? 4096 : length - offset;
        if (const auto len = sftp_write(file.Get(), (buffer.data() + offset), wLength); len != wLength) {
            return false;
        }
        offset += wLength;
    }
    return true;
}

bool SshSftpConnection::RemoveFile(const char* target) const {
    return Unlink(target);
}

bool SshSftpConnection::SoftLink(const char* target, const char* linkPath) const {
    if (sftp_symlink(sftp.Get(), target, linkPath) < SSH_OK) {
        return false;
    }
    return true;
}

bool SshSftpConnection::HardLink(const char* target, const char* linkPath) const {
    if (sftp_hardlink(sftp.Get(), target, linkPath) != SSH_OK) {
        return false;
    }
    return true;
}

bool SshSftpConnection::Unlink(const char* target) const {
    if (sftp_unlink(sftp.Get(), target) != SSH_OK) {
        return false;
    }
    return true;
}

const char *SshSftpConnection::ReadLink(const char *target) const {
    return sftp_readlink(sftp.Get(), target);
}

QStringList SshSftpConnection::DisplayDirContents(const SftpDir& dir) const {
    QStringList contentsList;
    sftp_attributes attributes {nullptr};
    // 阅读文件夹
    while ((attributes = sftp_readdir(sftp.Get(), dir.Get()))) {
        QString contents = QString::asprintf(
            "%s;%s;%u;%u;%llu;%u;%u;%s;%s;%u;%llu;%u;%u",
            attributes->name,
            attributes->longname,
            attributes->flags,
            attributes->type,
            attributes->size,
            attributes->uid,
            attributes->gid,
            attributes->owner,
            attributes->group,
            attributes->permissions,
            attributes->atime64,
            attributes->atime,
            attributes->atime_nseconds
        );
        contentsList.append(std::move(contents));
        sftp_attributes_free(attributes);
        attributes = nullptr;
    }

    if (!sftp_dir_eof(dir.Get())) {
        qCritical("Error: %s\n", ssh_get_error(sshSession.Get()));
    }
    return contentsList;
}

bool SshSftpConnection::Rename(const char *original, const char *newest) const {
    if (sftp_rename(sftp.Get(), original, newest) != SSH_OK) {
        return false;
    }
    return true;
}

void SshSftpConnection::connectSftp() {
    sshSession = SshSession(
        mSshLoginKeys.user, mSshLoginKeys.host,
        mSshLoginKeys.port, mSshLoginKeys.password);

    if (!sshSession.IsNull() && !sshSession.connect()) {
        qCritical("SshSftpConnection::connectSftp() 111, %s", ssh_get_error(sshSession.Get()));
        return;
    }
    InitSftp(sshSession);
    qDebug("Additional SFTP extensions provided by the server:\n");
    const auto count = sftp_extensions_get_count(sftp.Get());
    for (auto i = 0; i < count; i++) {
        qDebug("\t%s, version: %s\n",
               sftp_extensions_get_name(sftp.Get(), i),
               sftp_extensions_get_data(sftp.Get(), i));
    }
}
